package com.qianfeng.smartplatform.service.impl;

//
//                            _ooOoo_
//                           o8888888o
//                           88" . "88
//                           (| -_- |)
//                            O\ = /O
//                        ____/`---'\____
//                      .   ' \\| |// `.
//                       / \\||| : |||// \
//                     / _||||| -:- |||||- \
//                       | | \\\ - /// | |
//                     | \_| ''\---/'' | |
//                      \ .-\__ `-` ___/-. /
//                   ___`. .' /--.--\ `. . __
//                ."" '< `.___\_<|>_/___.' >'"".
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |
//                 \ \ `-. \_ __\ /__ _/ .-` / /
//         ======`-.____`-.___\_____/___.-`____.-'======
//                            `=---='
//
//         .............................................
//                  佛祖镇楼                  BUG辟易
//          佛曰:
//                  写字楼里写字间，写字间里程序员；
//                  程序人员写程序，又拿程序换酒钱。
//                  酒醒只在网上坐，酒醉还来网下眠；
//                  酒醉酒醒日复日，网上网下年复年。
//                  但愿老死电脑间，不愿鞠躬老板前；
//                  奔驰宝马贵者趣，公交自行程序员。
//                  别人笑我忒疯癫，我笑自己命太贱；
//


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.qianfeng.smartplatform.cache.CategoriesCache;
import com.qianfeng.smartplatform.cache.SceneCache;
import com.qianfeng.smartplatform.constants.ResultCode;
import com.qianfeng.smartplatform.dto.SysDeviceDto;
import com.qianfeng.smartplatform.event.DeviceColorCommandEvent;
import com.qianfeng.smartplatform.event.DevicePowerCommandEvent;
import com.qianfeng.smartplatform.exceptions.AddDataException;
import com.qianfeng.smartplatform.exceptions.QueryException;
import com.qianfeng.smartplatform.exceptions.UpdateException;
import com.qianfeng.smartplatform.mapper.SysDeviceMapper;
import com.qianfeng.smartplatform.pojo.*;
import com.qianfeng.smartplatform.service.SysDeviceService;
import com.qianfeng.smartplatform.utils.RequestUtil;
import com.qianfeng.smartplatform.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
 * Created by Jackiechan on 2022/5/30 9:41
 *
 * @author Jackiechan
 * @version 1.0
 * @since 1.0
 */
@Service
@Transactional
public class SysDeviceServiceImpl implements SysDeviceService {

    private ObjectMapper objectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    private ApplicationContext context;

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    private SceneCache sceneCache;

    @Autowired
    public void setSceneCache(SceneCache sceneCache) {
        this.sceneCache = sceneCache;
    }

    private CategoriesCache categoriesCache;

    @Autowired
    public void setCategoriesCache(CategoriesCache categoriesCache) {
        this.categoriesCache = categoriesCache;
    }

    private SysDeviceMapper sysDeviceMapper;

    @Autowired
    public void setSysDeviceMapper(SysDeviceMapper sysDeviceMapper) {
        this.sysDeviceMapper = sysDeviceMapper;
    }

    @Override
    public void addDevice(SysDeviceDto sysDeviceDto) {

        Assert.isTrue(!sysDeviceDto.isEmpty(CheckType.ADD), () -> {
            throw new AddDataException("传递的数据不完整", ResultCode.DATA_NULL);
        });

        //对设备的变化和分类进行校验
        //编号我们肯定会有一些规则,比如前面几位代表什么,中间几位代表,后面几位代码什么,编号不是随便乱码生成的,不同的企业会有不同的要求
        //添加的分类可能会不存在,我们需要对分类进行判断
        //获取到分类的id
        Long categyId = sysDeviceDto.getCategyId();
        //根据id去查询分类数据,如果能查到,就说明没有什么问题,查不到,就说明有问题,并且理论上只能继续生产还存在的分类数据
        //但是突然想起来,我们的分类数据在缓存中保存了一份,应该从缓存中查询,但是缓存中的数据都是集合,我们现在期望的是根据主键查询
        //我们期望的是一个key-value类型的保存格式,除非我们遍历集合,看看哪一条的id和我们的当前的分类id一样
        SysCategory category = categoriesCache.get(categyId);
        //分类必须存在,并且它的状态是启用状态
        Assert.isTrue(category != null && category.getStatus() == 1, () -> {
            throw new AddDataException("分类不存在", ResultCode.CATEGORY_NOT_EXIST);
        });
        //添加数据
        sysDeviceMapper.addDevice(sysDeviceDto);
    }

    @Override
    public int update2Sell(String deviceId) {
        //首先,这个设备的状态必须是生产状态才开以更新为销售
        Assert.hasText(deviceId, () -> {
            throw new UpdateException("主键不符合要求", ResultCode.ID_NOTALLOWED);
        });
        //1我们要先根据设备的id查询设备,得到的设备必须是状态为生产才可以更新
        SysDevice sysDevice = sysDeviceMapper.findByDeviceId(deviceId);
        Assert.notNull(sysDevice, () -> {
            throw new UpdateException("设备不存在", ResultCode.DEVICE_NOT_EXIST);

        });

        Assert.isTrue(sysDevice.getStatus() == 0, () -> {
            throw new UpdateException("设备状态不匹配", ResultCode.DEVICE_STATUS_NOT_MATCH);
        });

        return sysDeviceMapper.update2Sell(deviceId);
    }

    @Override
    public int bindDevice(String deviceId, Long sceneId) {
        //绑定设备是必须要登录的,所以用户的id我们可以从登录的用户中获取
        //获取当前登录的用户
        MyBaseUser sysUserInfo = SecurityUtils.getUserInfo(false);
        //如果用户没有传递场景,我们需要一个默认的场景,在我们的项目中,默认的id就是-1
        if (-1 != sceneId) {
            try {
                //查询用户的缓存
                List<SysScene> sceneList = sceneCache.getUserSceneCache().get(sysUserInfo.getUserId());
                //要判断场景是不是存在
                long count = sceneList.stream().filter(sysScene -> sysScene.getSceneId() == sceneId).count();
                //场景如果存在一定会有一条数据
                Assert.isTrue(count == 1, () -> {
                    throw new AddDataException("场景不存在", ResultCode.SCENE_NOT_EXIST);
                });
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        //判断设备是不是存在
        //我们需要根据用户传递的设备的id,查询到设备,然后看看一些数据状态是不是符合要求
        SysDevice device = sysDeviceMapper.findByDeviceId(deviceId);
        Assert.notNull(device, () -> {
            throw new AddDataException("设备不存在", ResultCode.DEVICE_NOT_EXIST);
        });
        //我们要先确定设备没有被绑定,如果绑定了就提示已经绑定
        Assert.isTrue(device.getStatus() != 2, () -> {
            throw new AddDataException("设备已经被绑定", ResultCode.DEVICE_ALREADY_BIND);
        });

        //绑定之前要检查状态,1是销售状态,3解绑状态
        Assert.isTrue(device.getStatus() == 1 || device.getStatus() == 3, () -> {
            throw new AddDataException("设备状态不匹配", ResultCode.DEVICE_STATUS_NOT_MATCH);
        });

        SysDevice sysDevice = new SysDevice();
        sysDevice.setDeviceId(deviceId);
        sysDevice.setBindUserId(sysUserInfo.getUserId());
        sysDevice.setBindTime(new Date());
        sysDevice.setSceneId(sceneId);
        //绑定设备
        return sysDeviceMapper.bindDevice(sysDevice);
    }

    @Override
    public PageInfo<SysDevice> findAllDevices(int page, int limit) {
        PageHelper.startPage(page, limit);
        //获取当前登录的用户
        MyBaseUser sysUserInfo = SecurityUtils.getUserInfo(false);
        //查询数据库
        List<SysDevice> deviceList = sysDeviceMapper.findAllByUserId(sysUserInfo.getUserId());
        //补全分类数据
        deviceList.forEach(sysDevice -> {
            //查询这个设备相关的分类
            SysCategory category = categoriesCache.get(sysDevice.getCategyId());
            //设置分类
            sysDevice.setCategory(category);
        });
        return new PageInfo<>(deviceList);
    }

    @Override
    public void sendControl(String deviceId, String command) {
        //假设发送命令,先获取到真正要发送的数据
        //根据我们的约定,获取到对应的命令,然后发送给设备
        //根据设备的id,找到这个设备对应的分类,然后找到分类对应的命令
        Long categoryId = sysDeviceMapper.getCategoryIdByDeviceId(deviceId);
        Assert.notNull(categoryId, () -> {
            throw new QueryException("设备不存在", ResultCode.ID_NOTALLOWED);
        });
        //然后根据发送过来的命令关键字,找到对应的真正的命令,然后再将真正的命令发送出去
        SysCategory category = categoriesCache.get(categoryId);
        //获取到能够发送给设备的命令
        String txCommand = category.getTxCommand();

        Assert.hasText(txCommand,()->{
            throw new QueryException("当前设备暂时不支持该命令", ResultCode.DEVICE_COMMAND_NOT_SUPPORT);
        });
        //将命令解析成map
        try {
            Map commandMap = objectMapper.readValue(txCommand, Map.class);
            //获取当前要发送的命令对应的真正的命令
            Map realCommandMap = (Map) commandMap.get(command);

            Assert.notEmpty(realCommandMap,()->{
                throw new QueryException("当前设备暂时不支持该命令", ResultCode.DEVICE_COMMAND_NOT_SUPPORT);
            });
            //获取到命令的类型
            Integer type = (Integer) realCommandMap.get("type");
            if (type == 1) {
                //发送对应的数据到继电器设备
                //通过事件机制,将数据转发给了websocket,请求由http转为websocket
                context.publishEvent(new DevicePowerCommandEvent(deviceId, (String) realCommandMap.get("command")));
            } else if (type == 2) {
                //发送变色
                context.publishEvent(new DeviceColorCommandEvent(deviceId, (String) realCommandMap.get("command")));

            }
            //不管你执行了什么操作,都要更新操作时间
            SysDevice sysDevice = new SysDevice();
            sysDevice.setDeviceId(deviceId);
            //更新操作事件
            sysDevice.setLastControlTime(new Date());
            sysDeviceMapper.updateDevice(sysDevice);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateDeviceOnlineOffLine(String deviceId, String ip) {
        //将要更新的数据封装到对象中
        SysDevice sysDevice = new SysDevice();
        sysDevice.setDeviceId(deviceId);
        if (ip != null) {
            sysDevice.setIsOnline(1L);
            sysDevice.setConnectTime(new Date());
            sysDevice.setCurrentConnectIp(ip);
            //带着ip就是上线,这是我们自己约定的
            //根据ip获取到位置
            String locationByIp = RequestUtil.getLocationByIp(ip);
            sysDevice.setConnectLocation(locationByIp);
        }else{
            //如果没有ip就说明是离线了
            sysDevice.setIsOnline(0L);
        }

        //更新数据
        sysDeviceMapper.updateDevice(sysDevice);
    }

}
